package com.caissa.springboot.starter.permission.aspect;

import cn.hutool.json.JSONObject;
import com.caissa.ecp.base.api.dto.UserInfoPo;
import com.caissa.ecp.base.api.dto.po.UserInfo;
import com.base.springboot.starter.kafka.service.KafkaService;
import com.base.springboot.starter.permission.annotations.AuditLog;
import com.base.springboot.starter.permission.constant.KafkaConstants;
import com.base.springboot.starter.permission.domain.AuditLogInfo;
import com.base.springboot.starter.permission.enums.AuditLogRecordTypeEnum;
import com.base.springboot.starter.permission.service.AesTokenUtil;
import com.base.springboot.starter.permission.service.TokenConstant;
import com.base.springboot.starter.permission.util.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * @author zhangqiang
 * @date 2021/8/12 PM2:11
 */
@Aspect
@Order(10)
@Slf4j
public class AuditLogAspect{
    @Autowired
    private KafkaService kafkaService;

    @Pointcut("@within(com.base.springboot.starter.permission.annotations.AuditLog) || @annotation(com.base.springboot.starter.permission.annotations.AuditLog)")
    public void auditLogPointcut(){}

    @Around("auditLogPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long time = System.currentTimeMillis();
        try {
            return joinPoint.proceed();
        } finally {
            try {
                AuditLogInfo log = createAuditLogInfo(joinPoint,(System.currentTimeMillis() - time),null);
                kafkaService.send(KafkaConstants.AUDIT_LOG + KafkaConstants.TOPIC, log);
            }catch (Exception e){
                log.error("AuditLogAspect 操作失败：" + e.getMessage());
            }
        }
    }

    /**
     * 异常通知，插入异常记录
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "auditLogPointcut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e){
        AuditLogInfo log = createAuditLogInfo(joinPoint,null,e);
        kafkaService.send(KafkaConstants.AUDIT_LOG + KafkaConstants.TOPIC, log);
    }

    /**
     * 封装审计日志对象
     *
     * @param joinPoint
     * @param time
     * @param e
     * @return
     */
    private AuditLogInfo createAuditLogInfo(JoinPoint joinPoint, Long time, Throwable e) {
        //方法执行完成后增加日志
        AuditLogInfo log = new AuditLogInfo();
        log.setAccessTime(time);
        if(e == null){
            log.setRecordType(AuditLogRecordTypeEnum.MANAGER_OK.getCode());
        }else {
            log.setRecordType(AuditLogRecordTypeEnum.MANAGER_ERROR.getCode());
            log.setExceptionDesc(e.getMessage());
        }
        // 请求方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        AuditLog auditLog = method.getAnnotation(AuditLog.class);
        if (auditLog != null) {
            if(auditLog.recordParams()){
                log.setMethodParams(getMethodParams(joinPoint,method));
            }
            log.setMethodDesc(auditLog.desc());
            log.setOperateType(auditLog.operateType().getCode());
            log.setApplication(auditLog.application());
        }
        UserInfoPo userInfo = this.getUserInfo();
        log.setCreateEmp(userInfo.getId()+"");
        log.setCreateEmpName(userInfo.getName());
        log.setPhone(userInfo.getPhone());
        HttpServletRequest httpServletRequest = SpringUtil.getHttpServletRequest();
        log.setAccessIp(getIpAddr(httpServletRequest));
        log.setAccessUrl(httpServletRequest.getRequestURI());
        log.setCreateTime(new Date());
        return log;
    }

    /**
     * 获取方法请求参数
     *
     * @param joinPoint
     * @param method
     * @return
     */
    private String getMethodParams(JoinPoint joinPoint,Method method) {
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        Object[] args = joinPoint.getArgs();
        if (args != null && paramNames != null) {
            String params = "";
            for (int i = 0; i < args.length; i++) {
                params += "  " + paramNames[i] + ": " + args[i];
            }
            return params;
        }
        return null;
    }


    /**
     * 获取HttpServletRequest
     *
     * @return
     */
    private UserInfoPo getUserInfo() {
        String token = SpringUtil.getHttpServletRequest().getHeader(TokenConstant.xToken);
        UserInfoPo userInfo = new UserInfoPo();
        if(token != null && token.length()>0){
            JSONObject userJsonObject = AesTokenUtil.getUserJsonObject(token);
            userInfo.setId(userJsonObject.getLong(TokenConstant.id));
            userInfo.setName(userJsonObject.getStr(TokenConstant.name));
            if (TokenConstant.B_TOKEN.equals(userJsonObject.getStr(TokenConstant.tokenType))) {
                userInfo.setPhone(userJsonObject.getStr(TokenConstant.loginCode));
            } else {
                userInfo.setPhone(userJsonObject.getStr(TokenConstant.phone));
            }
        }
        return userInfo;
    }

    private String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值，第一个ip才是真实ip
            if( ip.indexOf(",")!=-1 ){
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}
